package br.com.rookgraph.run;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import br.com.rookgraph.GenerateGraph;
import br.com.rookgraph.Graph;
import br.com.rookgraph.gui.JanelaArvores;
import br.com.rookgraph.gui.JanelaDeArena;
import br.com.rookgraph.gui.JanelaDeDuelo;
import br.com.rookgraph.gui.TuplaAlgoritmoArvore;
import br.com.rookgraph.utils.ControleExecucao;
import br.com.rookgraph.utils.EnumGeracaoArvore;
import br.com.rookgraph.utils.ExecuteHeuristicas;

public class ArenaAlgoritmos {

	private ExecuteHeuristicas algoritmo1;
	private ExecuteHeuristicas algoritmo2;
	
	private EnumGeracaoArvore arvore1;
	private EnumGeracaoArvore arvore2;
	
	private boolean hamiltonian;
	
	public ArenaAlgoritmos(ExecuteHeuristicas algoritmo1, ExecuteHeuristicas algoritmo2, EnumGeracaoArvore arvore1, EnumGeracaoArvore arvore2, boolean hamiltonian) {
		this.algoritmo1 = algoritmo1;
		this.algoritmo2 = algoritmo2;
		this.arvore1 = arvore1;
		this.arvore2 = arvore2;
		this.hamiltonian = hamiltonian;
	}
	
	public void executarDuelo(ControleExecucao execucao, int numV, int incEdge) {
		int qtdBranch1 = -1, qtdBranch2 = -1;
		
		JanelaDeDuelo.limpar();
		JanelaDeDuelo.logar("COMPARAÇÃO DE HEURÍSTICAS: " + algoritmo1.toString() + " vs. " + algoritmo2.toString());
		JanelaDeDuelo.logar( "\n *****  Resultado *****");
		JanelaDeDuelo.logar( "Grafo\t" + algoritmo1 + "\t" + algoritmo2);

		while (execucao.continua()) {
			long tempo1 = 0, tempo2 = 0;
			
			GenerateGraph generateGraph = new GenerateGraph();
			Graph graph = generateGraph.generateWhit(numV, incEdge, this.hamiltonian);
			
			int qtdBranchTreeInicial1 = 0;
			int qtdBranchTreeInicial2 = 0;
			
			int raiz = new Random().nextInt(numV) + 1;
			
			Graph treeIni1 = null;
			Graph treeIni2 = null;
			if (this.arvore1.equals(this.arvore2)) {
				treeIni1 = this.arvore1.getTree(graph, raiz);
				treeIni2 = treeIni1;
			} else {
				treeIni1 = this.arvore1.getTree(graph, raiz);
				treeIni2 = this.arvore2.getTree(graph, raiz);
			}
			
			qtdBranchTreeInicial1 = treeIni1.getBranchs().length;
			qtdBranchTreeInicial2 = treeIni2.getBranchs().length;

			if (qtdBranchTreeInicial1 > 0 && qtdBranchTreeInicial2 > 0) {
				tempo1 = System.currentTimeMillis();
				Graph result1 = algoritmo1.execute(graph, clone(treeIni1));
				tempo1 = System.currentTimeMillis() - tempo1;
				
				tempo2 = System.currentTimeMillis();
				Graph result2 = algoritmo2.execute(graph, clone(treeIni2));
				tempo2 = System.currentTimeMillis() - tempo2;
				
				qtdBranch1 = result1 == null ? -1 : result1.getBranchs().length;
				qtdBranch2 = result2 == null ? -1 : result2.getBranchs().length;
				
				// Esse if deve exibir e contabilizar somente quando foi possível fazer a redução de branches com pelo menos uma heurística
				if (qtdBranchTreeInicial1 > qtdBranch1 || qtdBranchTreeInicial1 > qtdBranch2) {	
					execucao.marcarFimIteracao(qtdBranch1, qtdBranch2, tempo1, tempo2);
					execucao.exibirDadosIteracao();
				}
			}
		}
		
		JanelaDeDuelo.logar("\n");
		JanelaDeDuelo.logar(execucao.getQtdResultado1Melhor() + " vitórias de " + algoritmo1 + "/" + this.getArvore1().getDescricao());
		JanelaDeDuelo.logar(execucao.getQtdResultado2Melhor() + " vitórias de " + algoritmo2 + "/" + this.getArvore2().getDescricao());
		JanelaDeDuelo.logar(execucao.getQtdEmpates()+ " Empates: ");
		
		execucao.zerarContadores();
		
		JanelaDeDuelo.logar("\nFim.");
	}
	
	public static void executarMultArvores(String filePath, String fileName, Enumeration<EnumGeracaoArvore> configuracoes) throws IOException, FileNotFoundException, CloneNotSupportedException {
		ReadFileGraph readFileGraph = new ReadFileGraph(filePath);
		
		Graph graph = readFileGraph.read();

		File fileResult = null;
		try {
			fileResult = new File("resources/tree-result/" + fileName + "-result.csv");
			fileResult.createNewFile();
		} catch (IOException e) {
			try {
				fileResult = new File("tree-result/" + fileName + "-result.csv");
				fileResult.createNewFile();
			} catch (Exception e2) {
				JanelaArvores.logar("Erro ao gravar arquivo: " + fileResult.getAbsolutePath());
				
				return;
			}
		}
		
	    FileOutputStream writer = new FileOutputStream(fileResult);
	    OutputStreamWriter wStream = new OutputStreamWriter(writer);
	    BufferedWriter wBuffer = new BufferedWriter(wStream);
	    
	    wBuffer.append(";\n;\n");
	    
	    wBuffer.append("Graph;");
	    for (EnumGeracaoArvore h : EnumGeracaoArvore.values()) {
	    	wBuffer.append(h + ";");
	    }

		Collection<EnumGeracaoArvore> lista = new ArrayList<EnumGeracaoArvore>();
		while (configuracoes.hasMoreElements()) {
			lista.add(configuracoes.nextElement());
		}
		
		int i = 1;
		while (graph != null) {
			wBuffer.newLine();
			wBuffer.append("#" + i + ";");
			
			JanelaArvores.logar("Graph #" + i);
			
			for (EnumGeracaoArvore arv : EnumGeracaoArvore.values()) {
				if (lista.contains(arv)) {
					Graph graphTree = arv.getTree(graph, 1);
					wBuffer.append(Integer.toString(graphTree.getBranchs().length));
				} else {
					wBuffer.append("--");
				}
				
				wBuffer.append(";");
			}
			
			i++;
			
			graph = readFileGraph.read();
		}
		
		wBuffer.close();
		wStream.close();
		writer.close();
		
		JanelaArvores.logar("\nArquivo de resultados gravado: " + fileResult.getAbsolutePath());
	}
	

	public static void executarMultHeuristicas(String caminhoInput, String caminhoOutput, Enumeration<TuplaAlgoritmoArvore> configuracoes) throws IOException, FileNotFoundException, CloneNotSupportedException {
		ReadFileGraph readFileGraph = new ReadFileGraph(caminhoInput);
		Graph graph = readFileGraph.read();
		
		HashMap<ExecuteHeuristicas, Long> mapaTempos = new HashMap<ExecuteHeuristicas, Long>();
		
		File fileResult = null;
		try {
			fileResult = new File("resources/graphs-result/" + caminhoOutput + "-result.csv");
			fileResult.createNewFile();
		} catch (IOException e) {
			try {
				fileResult = new File("graphs-result/" + caminhoOutput + "-result.csv");
				fileResult.createNewFile();
			} catch (Exception e2) {
				JanelaDeArena.logar("Erro ao gravar arquivo: " + fileResult.getAbsolutePath());
				
				return;
			}
		}
		
	    FileOutputStream writer = new FileOutputStream(fileResult);
	    OutputStreamWriter wStream = new OutputStreamWriter(writer);
	    BufferedWriter wBuffer = new BufferedWriter(wStream);
	    
	    wBuffer.append(";\n;\n");
	    
	    wBuffer.append("Graph;Inicial;");
	    for (ExecuteHeuristicas h : ExecuteHeuristicas.values()) {
	    	if (h.equals(ExecuteHeuristicas.NENHUMA)) continue;
	    	wBuffer.append(h + ";");
	    }

		Map<ExecuteHeuristicas, EnumGeracaoArvore> mapaConfiguracoes = new HashMap<ExecuteHeuristicas, EnumGeracaoArvore>();
		while (configuracoes.hasMoreElements()) {
			TuplaAlgoritmoArvore elemento = configuracoes.nextElement();
			mapaConfiguracoes.put(elemento.getHeuristica(), elemento.getArvore());
		}
		
		int i = 1;
		while (graph != null) {
			wBuffer.newLine();
			wBuffer.append("#" + i + ";");
			
//			System.out.println("Trocas do Grafo: "+i);
			
			JanelaDeArena.logar("Graph #" + i);
			Graph graphResult;
			
			Map<EnumGeracaoArvore, Graph> mapaArvores = new HashMap<EnumGeracaoArvore, Graph>();
			for (EnumGeracaoArvore a : EnumGeracaoArvore.values()) {
				mapaArvores.put(a, a.getTree(graph, 1));
			}
			
			// A adição de uma coluna da maneira abaixo só faz sentido quando todos os métodos escolhidos para geração da árvore inicial forem os mesmos
			 int qtdInicialBranches = mapaArvores.values().iterator().next().getBranchs().length;
			 wBuffer.append(Integer.toString(qtdInicialBranches) + ";");
			
			// calculando a quantidade de articulações e armazenando 
			//graph.articulacoes = ProcessadorArticulacoes.identificarArticulacoes(graph, 1);
			//wBuffer.append(Integer.toString(graph.getQtdArticulacoes())+";");
			
			
			for (ExecuteHeuristicas h : ExecuteHeuristicas.values()) {
				if (h.equals(ExecuteHeuristicas.NENHUMA)) continue;
				
				if (mapaConfiguracoes.keySet().contains(h)) {
					Graph graphTree = mapaArvores.get(mapaConfiguracoes.get(h));
					
	// escreve o nome da heurística que está sendo executado
	//				System.out.println("Heurística: "+h.name());
	//				System.out.println(graphTree);
					
					long t = System.currentTimeMillis();
					graphResult = h.execute((Graph)graph.clone(), (Graph)graphTree.clone());
					t = System.currentTimeMillis() - t;
					
					wBuffer.append(Integer.toString(graphResult.getBranchs().length));
					
					if (!mapaTempos.containsKey(h)) {
						mapaTempos.put(h, t);
					} else {
						mapaTempos.put(h, mapaTempos.get(h) + t);
					}
				} else {
					wBuffer.append("--");
				}
				
				wBuffer.append(";");
			}
			i++;
			
			graph = readFileGraph.read();
		}
		
		wBuffer.newLine();
		wBuffer.append("Tempos;-;");
		for (ExecuteHeuristicas h : ExecuteHeuristicas.values()) {
			if (h.equals(ExecuteHeuristicas.NENHUMA)) continue;
			if (mapaTempos.containsKey(h)) {
				wBuffer.append(Float.toString(mapaTempos.get(h)).replace(".", ",") + ";");
			} else {
				wBuffer.append("-;");
			}
		}
		
		wBuffer.close();
		wStream.close();
		writer.close();
	
		JanelaDeArena.logar("\nArquivo de resultados gravado:\n" + fileResult.getAbsolutePath());
	}
	
	/**
	 * Retorna um clone do grafo passado, uma nova instancia
	 * com a mesma matriz de adjacencia.
	 *
	 * @param treeLarge
	 * @return clone da tree
	 */
	public static Graph clone(Graph graph) {
		try {
			return (Graph) graph.clone();
		} catch (CloneNotSupportedException e) {
			throw new RuntimeException(e);
		}
	}

	public ExecuteHeuristicas getAlgoritmo1() {
		return algoritmo1;
	}

	public void setAlgoritmo1(ExecuteHeuristicas algoritmo1) {
		this.algoritmo1 = algoritmo1;
	}

	public ExecuteHeuristicas getAlgoritmo2() {
		return algoritmo2;
	}

	public void setAlgoritmo2(ExecuteHeuristicas algoritmo2) {
		this.algoritmo2 = algoritmo2;
	}

	public EnumGeracaoArvore getArvore1() {
		return arvore1;
	}

	public void setArvore1(EnumGeracaoArvore arvore1) {
		this.arvore1 = arvore1;
	}

	public EnumGeracaoArvore getArvore2() {
		return arvore2;
	}

	public void setArvore2(EnumGeracaoArvore arvore2) {
		this.arvore2 = arvore2;
	}

}
